package org.gitman.agent.platform;

import java.awt.Color;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import org.gitman.agent.exceptions.BadArgsException;
import org.gitman.agent.exceptions.NoGraphException;
import org.gitman.agent.gui.GraphDisplPanel;
import org.gitman.agent.gui.GraphPanel;

import com.mxgraph.model.mxCell;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxConstants;
import com.mxgraph.view.mxGraph;

public class GraphEditor {
	private boolean isEdited;
	private mxGraph graph;
	private Object parent;
	private GraphDisplPanel graphDisplPanel;
	private mxGraphComponent graphComponent;
	
	public GraphEditor(){
		isEdited = false;
		graph = null;
	}
	
	public boolean isEdited(){
		return isEdited;
	}
	
	public void startEditing() throws NoGraphException{
		if(graph == null){
			throw new NoGraphException("No Graph. \nCreate  (File->New Grap) \nor load (File->Load Graph)");
		}
		isEdited = true;
		graphComponent.setConnectable(true);
		graph.setCellsDisconnectable(true);
		for(Object o : graph.getChildCells(graph.getDefaultParent(), true, true)){
			((mxCell)o).setConnectable(true);
		}
	}
	
	public void finishEditing(){
		isEdited = false;
		graphComponent.setConnectable(false);
		graph.setCellsEditable(false);
		graph.setCellsDisconnectable(false);
		for(Object o : graph.getChildCells(graph.getDefaultParent(), true, true)){
			((mxCell)o).setConnectable(false);
		}
	
	}

	public mxGraph getGraph(){
		return graph;
	}
	
	public void setGraph(mxGraph graph){
		this.graph = graph;
		newGraphInEditor();
	}
	
	public void createGraph(){
		graph = new mxGraph();
		newGraphInEditor();
	}
	
	private void newGraphInEditor(){
		isEdited = false;
		parent = graph.getDefaultParent();
		graphComponent = new mxGraphComponent(graph);
		graph.setCellsEditable(false);
		graph.setCellsResizable(false);
		graph.setAllowDanglingEdges(false);
		graphComponent.setConnectable(false);
		graphDisplPanel.setGraphComponent(graphComponent);
	}

	public void setDisplay(GraphDisplPanel graphDisplPanel) {
		this.graphDisplPanel = graphDisplPanel;	
	}

	public mxGraphComponent getGraphComponent(){
		return graphComponent;
	}
	
	public void generateGraph(int v, int minE, int maxE, int randP, Method2GenerateEdges meth, Method2GenerateVertices methV) throws NoGraphException, BadArgsException{
		if(graph == null){
			throw new NoGraphException("No Graph. \nCreate  (File->New Grap) \nor load (File->Load Graph)");
		}
		if(minE <= 0 || minE > maxE || v <= minE || 0 > randP || randP > 100){
			throw new BadArgsException("Wrong arguments. Arguments must meet limitations:\nv > maxE >= minE > 0 and 0 <= randPercentage <= 100");
		}
		
		int SIZE = GraphDisplPanel.SIZE;
		int width = graphDisplPanel.getWidth() - 2*SIZE;
		int height = graphDisplPanel.getHeight() - 2*SIZE;
		
		
		Random rand = new Random(Calendar.getInstance().getTimeInMillis());
		Vx [] vertices = new Vx[v];
		
		
		if(methV == Method2GenerateVertices.Random){
			for(int i =0;i<v;i++){
				int x = rand.nextInt(width);
				int y = rand.nextInt(height);
				vertices[i] = new Vx(x,y, graphDisplPanel.insertVertex(x,y));
			}
		}else if(methV == Method2GenerateVertices.Grid){
			int rows = (int) Math.round(Math.sqrt(((double)(v*height*1.0))/((double)(width*1.0))));
			rows = (rows == 0) ? 1 : rows;
			int cols = v/rows;
			if(rows*cols < v){
				cols++;
			}
				
			int stepX = width/(cols-1);
			int stepY = height/(rows-1);
			
			for(int i =0 ;i<rows;i++){
				for(int j = 0;j<cols;j++){
					int index = i*cols + j;
					if(index < v){
						int x = stepX*j;
						int y = stepY*i;
						vertices[index] = new Vx(x,y, graphDisplPanel.insertVertex(x, y));
					}
				}		
			}
		
		}
		if(meth == Method2GenerateEdges.Random){
			for(int i =0;i<v;i++){
				int conn = minE + rand.nextInt(maxE - minE);
				Set<Object> set = new HashSet<>();
				while(set.size() < conn){
					Object o = vertices[rand.nextInt(v)].o;
					if(!set.contains(o) && o != vertices[i].o){
						set.add(o);
					}
				}
				for(Object o : set){
					graphDisplPanel.insertEdge(vertices[i].o, o);
				}				
			}			
		}else if(meth == Method2GenerateEdges.Neighbors){			
			for(int i =0;i<v;i++){
				int conn = minE + rand.nextInt(maxE - minE);
				Set<Vx> set = new HashSet<>(Arrays.asList(vertices));
				set.remove(vertices[i]);
				Vx me = vertices[i];
				Set<Object> neigh = new HashSet<>();
				while(neigh.size() < conn){
					Vx n = null;
					double min = Double.MAX_VALUE;
					for(Vx vx : set){
						double calc = me.getDistFrom(vx);
						if(calc < min){
							min = calc;
							n = vx;
						}
					}
					neigh.add(n.o);
					set.remove(n);
				}				
				for(Object o : neigh){
					graphDisplPanel.insertEdge(vertices[i].o, o);
				}
			}			
		}else if (meth == Method2GenerateEdges.Mixed){
			for(int i =0;i<v;i++){
				int total = minE + rand.nextInt(maxE - minE);
				int rd = (int) Math.round((double)(randP*total*1.0)/100.0);
				int nei = total - rd;
				
				Set<Vx> set = new HashSet<>(Arrays.asList(vertices));
				set.remove(vertices[i]);
				Vx me = vertices[i];
				Set<Object> neigh = new HashSet<>();
				while(neigh.size() < nei){
					Vx n = null;
					double min = Double.MAX_VALUE;
					for(Vx vx : set){
						double calc = me.getDistFrom(vx);
						if(calc < min){
							min = calc;
							n = vx;
						}
					}
					neigh.add(n.o);
					set.remove(n);
				}
				while(neigh.size() < total){
					Object [] arr = set.toArray();
					Vx vx = (Vx)arr[rand.nextInt(arr.length)];
					set.remove(vx);
					neigh.add(vx.o);
				}				
				for(Object o : neigh){
					graphDisplPanel.insertEdge(vertices[i].o, o);
				}
			}	
		}
		
		if(isEdited){
			startEditing();
		}else{
			finishEditing();
		}
	}

	private class Vx{
		public int x,y;
		public Object o;
		public Vx(int x, int y, Object o){
			this.x = x;
			this.y = y;
			this.o = o;
		}
		
		public double getDistFrom(Vx v){
			return Math.sqrt((v.x-x)*(v.x-x)+(v.y-y)*(v.y-y));			
		}
	}

}
